"""
The module with all the tests for the functions of misc.py
"""
import unittest as ut
import sys
import os
import tempfile
import uuid
import logging
import time

import basicVdsTest as vdst
from confUtils import Validate

#Made so that log line wouldn't mess up display
logging.basicConfig(filename='/dev/null', filemode='w')

sys.path.append("../../")
import storage.misc as misc
import constants
import binascii

#TODO : NOT tested:
#   propagateError - Does nothing testable
#   logException - Does nothing testable
#   panic - Uses sys.exit which will crash the tester
#   fileexists - No idea how to simulate a test case
#   ddWatchCopy - Tests give out weird errors. Need to sort it out
#   ddCopy - Is only a wrapper for ddWatchCopy
#   rotateFiles(persist) - Not tests are run when argument persist=True
#   persistFile - No idea how to simulate a test case
#   dmRemoveMapping - No idea how to simulate a test case

#?  Performance issue in watch* functions. if idle is big function hangs for no reason.

class RotateFiles(vdst.BasicVDSTest):
    def testNonExistingDir(self, persist=False):
        """
        Tests that the method fails correctly when given a non existing dir.
        """
        self.assertRaises(OSError, misc.rotateFiles, "/I/DONT/EXIST", "prefix", 2, persist=persist)

    def testEmptyDir(self, persist=False):
        """
        Test that when given an empty dir the rotator works correctly.
        """
        prefix = "prefix"
        dir = tempfile.mkdtemp()

        misc.rotateFiles(dir, prefix, 0, persist=persist)

        os.rmdir(dir)

    def testFullDir(self, persist=False):
        """
        Test that rotator does it's basic functionality.
        """
        #Prepare
        prefix = "prefix"
        stubContent = "Multiple exclamation marks,\" he went on, shaking his head, \"are a sure sign of a diseased mind."
        dir = tempfile.mkdtemp()
        gen = 10

        expectedDirContent = []
        for i in range(gen):
            fname = "%s.txt.%d" % (prefix, i)
            expectedDirContent.append("%s.txt.%d" % (prefix, i + 1))
            f = open(os.path.join(dir, fname), "wb")
            f.write(stubContent)
            f.flush()
            f.close()

        #Rotate
        misc.rotateFiles(dir, prefix, gen, persist=persist)

        #Test result
        currentDirContent = os.listdir(dir)
        expectedDirContent.sort()
        currentDirContent.sort()
        try:
            self.assertEquals(currentDirContent, expectedDirContent)
        finally:
            #Clean
            for f in os.listdir(dir):
                os.unlink(os.path.join(dir, f))
            os.rmdir(dir)

class ParseHumanReadableSize(vdst.BasicVDSTest):
    def testValidInput(self):
        """
        Test that the method parses size correctly if given correct input.
        """
        for i in range (1, 1000):
            for schar, pow in [("T", 40), ("G", 30), ("M", 20), ("K", 10)]:
                self.assertEquals(misc.parseHumanReadableSize("%d%s" % (i, schar)), (2**pow) * i)

    def testInvalidInput(self):
        """
        Test that parsing handles invalid input correctly
        """
        self.assertEquals(misc.parseHumanReadableSize("T"), 0)
        self.assertEquals(misc.parseHumanReadableSize("TNT"), 0)
        self.assertRaises(AttributeError, misc.parseHumanReadableSize, 5)
        self.assertEquals(misc.parseHumanReadableSize("4.3T"), 0)

class DdWatchCopy(vdst.BasicVDSTest): #Doesn't really work dd gives me a headache
    def testCopySUDO(self):
        """
        Like testCopy but with SUDO.
        """
        self.testCopy(True)

    def testNonAlignedCopy(self, sudo=False):
        """
        Test that copying a file with odd length works.
        """

        data = '- "What\'re quantum mechanics?"' + \
               '- "I don\'t know. People who repair quantums, I suppose."'

        # Make sure the length is appropriate
        if (len(data) % 512) == 0:
            data += "!"

        srcFd, srcPath = tempfile.mkstemp()
        f = os.fdopen(srcFd, "wb")
        f.write(data)
        f.flush()
        f.close()
        os.chmod(srcPath, 0666)

        #Get a tempfilename
        dstFd, dstPath = tempfile.mkstemp()
        os.chmod(dstPath, 0666)

        #Copy
        rc, out, err = misc.ddWatchCopy(srcPath, dstPath, None, None, len(data), sudo=sudo)

        #Get copied data
        readData = open(dstPath).read()

        #clean
        os.unlink(dstPath)
        os.unlink(srcPath)

        # Compare
        self.assertEquals(readData, data)

    def testCopy(self, sudo=False):
        """
        Test that regular copying works.
        """
        #Prepare source
        data = "Everything starts somewhere, though many physicists disagree." + \
               "But people have always been dimly aware of the problem with the start of things." + \
               "They wonder how the snowplough driver gets to work, or how the makers of dictionaries look up the spelling of words."
        data *= 512 #Makes sure we divide with 512

        srcFd, srcPath = tempfile.mkstemp()
        f = os.fdopen(srcFd, "wb")
        f.write(data)
        f.flush()
        f.close()
        os.chmod(srcPath, 0666)

        #Get a tempfilename
        dstFd, dstPath = tempfile.mkstemp()
        os.chmod(dstPath, 0666)

        #Copy
        rc, out, err = misc.ddWatchCopy(srcPath, dstPath, None, None, len(data), sudo=sudo)

        #Get copied data
        readData = open(dstPath).read()

        #clean
        os.unlink(dstPath)
        os.unlink(srcPath)

        #Comapre
        self.assertEquals(readData, data)

    def testNonExistingFileSUDO(self):
        """
        Like testNonExistingFile but with SUDO.
        """
        self.testNonExistingFile(True)

    def testNonExistingFile(self, sudo=False):
        """
        Test that trying to copy a non existing file raises the right exception.
        """
        #Get a tempfilename
        srcFd, srcPath = tempfile.mkstemp()
        os.unlink(srcPath)

        #Copy
        self.assertRaises(misc.se.MiscBlockWriteException, misc.ddWatchCopy, srcPath, "/tmp/tmp", None, None, 100, sudo=sudo)

    def testStopSUDO(self):
        """
        Like testStop but with SUDO.
        """
        self.testStop(True)

    def testStop(self, sudo=False):
        """
        Test that stop really stops the copying process.
        """

        start = time.time()
        #I assume that copying that much data should take more then a second but it really depends on the computers memory speed.
        #If you have a better idea. Change this.
        misc.ddWatchCopy("/dev/zero", "/dev/null", lambda : True, 1, 10, sudo=sudo)
        end = time.time()
        if end - start > 2:
            self.fail("Copying didn't stopped!")

class ValidateN(vdst.BasicVDSTest):
    def testValidInput(self):
        """
        Test cases that the validator should validate.
        """
        try:
            value = 1
            misc.validateN(value, "a")
            value = "1"
            misc.validateN(value, "a")
            value = 1.0
            misc.validateN(value, "a")
            value = "471902437190237189236189"
            misc.validateN(value, "a")
        except misc.se.InvalidParameterException:
            self.fail("Failed while validating a valid value '%s'" % value)

    def testInvalidInput(self):
        """
        Test that the validator doesn't validate illegal input.
        """
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, "A", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, "-1", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, -1, "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, "4.3", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, "", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, "*", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateN, "2-1", "a")

class ValidateInt(vdst.BasicVDSTest):
    def testValidInput(self):
        """
        Test cases that the validator should validate.
        """
        try:
            value = 1
            misc.validateInt(value, "a")
            value = -1
            misc.validateInt(value, "a")
            value = "1"
            misc.validateInt(value, "a")
            value = 1.0
            misc.validateInt(value, "a")
            value = "471902437190237189236189"
            misc.validateInt(value, "a")
        except misc.se.InvalidParameterException:
            self.fail("Failed while validating a valid value '%s'" % value)

    def testInvalidInput(self):
        """
        Test that the validator doesn't validate illegal input.
        """
        self.assertRaises(misc.se.InvalidParameterException, misc.validateInt, "A", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateInt, "4.3", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateInt, "", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateInt, "*", "a")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateInt, "2-1", "a")

class ValidateUuid(vdst.BasicVDSTest): # Why not use a regex? -> "^[A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}$"
    def testValidInput(self):
        """
        Test if the function succeeds in validating valid UUIDs.
        """
        for i in range(1000):
            tmpUuid = uuid.generate()
            try:
                misc.validateUUID(tmpUuid)
            except misc.se.InvalidParameterException:
                self.fail("Could not parse VALID UUID '%s'" % tmpUuid)

    def testInvalidInputNotHex(self):
        """
        Test that validator detects when a non HEX char is in the input.
        """
        self.assertRaises(misc.se.InvalidParameterException, misc.validateUUID, "Dc08ff668-4072-4191-9fbb-f1c8f2daz333")

    def testWrongLength(self):
        """
        Test that the validator detects when the input is not in the correct length
        """
        self.assertRaises(misc.se.InvalidParameterException, misc.validateUUID, "Dc08ff668-4072-4191-9fbb-f1c8f2daa33")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateUUID, "Dc08ff668-4072-4191-9fb-f1c8f2daa333")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateUUID, "Dc08ff68-4072-4191-9fbb-f1c8f2daa333")
        self.assertRaises(misc.se.InvalidParameterException, misc.validateUUID, "Dc08ff668-4072-4191-9fbb-f1c8f2daa3313")

class UuidPack(vdst.BasicVDSTest):
    def test(self):
        """
        Test that the uuid that was packed can be unpacked without being changed
        """
        for i in range(1000):
            origUuid = uuid.generate()
            packedUuid = misc.packUuid(origUuid)
            self.assertEquals(misc.unpackUuid(packedUuid), origUuid)

class Checksum(vdst.BasicVDSTest):
    def testConsistency(self):
        """
        Test if when given the same input in different times the user will get the same checksum.
        """
        data = open("/dev/urandom", "rb").read(50)
        self.assertEquals(misc.checksum(data, 16), misc.checksum(data, 16))

    def testUniqueness(self):
        """
        Tests that in a probable way no 2 inputs get the same checksum.
        """

        for i in range(1000):
            data1 = open("/dev/urandom", "rb").read(50)
            data2 = open("/dev/urandom", "rb").read(50)

            if data1 == data2:
                continue

            cs1 = misc.checksum(data1, 16)
            cs2 = misc.checksum(data2, 16)

            if cs1 == cs2:
                self.fail("Inputs '%s' and '%s' had the same checksum '%s'" % (binascii.b2a_hex(data1), binascii.b2a_hex(data2), cs1))


class ParseBool(vdst.BasicVDSTest):
    def testValidInput(self):
        """
        Compare valid inputs with expected results.
        """
        self.assertEquals(misc.parseBool(True), True)
        self.assertEquals(misc.parseBool(False), False)
        self.assertEquals(misc.parseBool("true"), True)
        self.assertEquals(misc.parseBool("tRue"), True)
        self.assertEquals(misc.parseBool("false"), False)
        self.assertEquals(misc.parseBool("fAlse"), False)
        self.assertEquals(misc.parseBool("BOB"), False)

    def testInvalidInput(self):
        """
        See that the method is consistent when giver invalid input.
        """
        self.assertRaises(AttributeError, misc.parseBool, 1)
        self.assertRaises(AttributeError, misc.parseBool, None)

class AlignData(vdst.BasicVDSTest):
    def test(self):
        """
        Test various inputs and see that they are correct.
        """
        self.assertEquals(misc._alignData(100, 100), (4, 25, 25))
        self.assertEquals(misc._alignData(512, 512), (512, 1, 1))
        self.assertEquals(misc._alignData(1, 1024), (1, 1, 1024))
        self.assertEquals(misc._alignData(10240, 512), (512, 20, 1))
        self.assertEquals(misc._alignData(1, 1), (1, 1, 1))

class WriteBlockSUDO(vdst.BasicVDSTest):
    def _alignData(self, data, blockSize=512): #Ask why alignment in misc is not like mine.
        """
        Make sure that *data* is aligned to *blockSize*

        :returns: an aligned version of *data*.
        """
        return data + ((blockSize - (len(data) % blockSize)) * "\0")

    def testWriteToNewFileSUDO(self):
        """
        Like testWriteToInvalidFile but with SUDO
        """
        self.testWriteToInvalidFile(True)

    def testWriteToNewFile(self, sudo=False):
        """
        Test that writing to a non existing file works.
        """
        #Prepare
        data = "Bishops move diagonally. That's why they often turn up where the kings don't expect them to be."
        dataLen = len(data)
        fd, path = tempfile.mkstemp() #Used just so I can get a file I know doesn't exist
        os.unlink(path)

        #Make File
        misc.writeblockSUDO(path, 0, dataLen, data, sudo)

        #Get written data
        f = open(path, "rb")
        fileData = f.read()
        f.close()
        os.unlink(path)

        # Compare
        expectedData = self._alignData(data)
        self.assertEquals(fileData, expectedData)

    def testWriteToExistingFileSUDO(self):
        """
        Like testWriteToExistingFile but with SUDO.
        """
        self.testWriteToExistingFile(True)

    def testWriteToExistingFile(self, sudo=False):
        """
        Test that appending to existing files works.
        """
        #Prepare
        data = "Are you a philosopher? Where's your sponge?"
        dataLen = len(data)
        fd, path = tempfile.mkstemp()
        f = os.fdopen(fd, "wb")
        f.write(data)
        f.flush()
        f.close()

        #Append to file
        misc.writeblockSUDO(path, 512, dataLen, data, sudo)

        #Get written data
        f = open(path, "rb")
        fileData = f.read()
        f.close()
        os.unlink(path)

        #Compare
        expectedData = self._alignData(data) * 2
        self.assertEquals(fileData, expectedData)

    def testInvalidOffsetSUDO(self):
        """
        Like testInvalidOffset but with SUDO.
        """
        self.testInvalidOffset(True)

    def testInvalidOffset(self, sudo=False):
        """
        Test that method fails correctly when give invalid offset.
        """
        self.assertRaises(misc.se.MiscBlockWriteException, misc.writeblockSUDO, "/dev/null", 500, 512, "NOTHING", sudo)

    def testInvalidTextSUDO(self):
        """
        Like testInvalidText but with SUDO.
        """
        self.testInvalidText(True)

    def testInvalidText(self, sudo=False):
        """
        Test that method fails correctly when given invalid text.
        """
        self.assertRaises(TypeError, misc.writeblockSUDO, "/dev/null", 512, 512, None, sudo)
        self.assertRaises(TypeError, misc.writeblockSUDO, "/dev/null", 512, 512, 58, sudo)

    def testWriteToInvalidFileSUDO(self):
        """
        Like testWriteToExistingFile but with SUDO.
        """
        self.testWriteToExistingFile(True)

    def testWriteToInvalidFile(self, sudo=False):
        """
        Test that when you try to write to a file you can't the correct exception is raised.
        """
        #No one can write to urandom
        #self.assertRaises(OSError, misc.writeblockSUDO, "/dev/urandom", 512, 512, "DATA!!", sudo)
        self.assertRaises(misc.se.MiscBlockWriteException, misc.writeblockSUDO, "/dev/urandom", 512, 512, "DATA!!", sudo)

class ValidateDDBytes(vdst.BasicVDSTest):
    def testValidInputTrue(self):
        """
        Test that it works when given valid and correct input.
        """
        count = 802
        cmd = [constants.EXT_DD, "bs=1", "if=/dev/urandom", 'of=/dev/null', 'count=%d' % count]
        rc, out, err = misc.execCmd(cmd, sudo=False)

        self.assertTrue(misc.validateDDBytes(err, count))

    def testValidInputFalse(self):
        """
        Test that is work when given valid but incorrect input.
        """
        count = 802
        cmd = [constants.EXT_DD, "bs=1", "if=/dev/urandom", 'of=/dev/null', 'count=%d' % count]
        rc, out, err = misc.execCmd(cmd, sudo=False)

        self.assertFalse(misc.validateDDBytes(err, count + 1))

    def testInvalidInput(self):
        """
        Test that the method handles wired input.
        """
        self.assertFalse(misc.validateDDBytes(["I AM","PRETENDING TO", "BE DD"], "BE"))
        self.assertFalse(misc.validateDDBytes(["I AM","PRETENDING TO", "BE DD"], 32))

class ReadBlockSudo(vdst.BasicVDSTest):
    def _createTempFile(self, neededFileSize, writeData):
        """
        Create a temp file with the data in *writeData* written continuously in it.

        :returns: the path of the new temp file.
        """
        dataLength = len(writeData)

        fd, path = tempfile.mkstemp()
        f = os.fdopen(fd, "wb")

        written = 0
        while written < neededFileSize:
            f.write(writeData)
            written += dataLength
        f.close()
        return path

    def testValidInputSUDO(self):
        """
        The same as testValidInput but with SUDO
        """
        self.testValidInput(True)

    def testValidInput(self, sudo=False):
        """
        Test that when all arguments are correct the method works smoothly.
        """
        writeData = "DON'T THINK OF IT AS DYING, said Death. JUST THINK OF IT AS LEAVING EARLY TO AVOID THE RUSH."
        dataLength = len(writeData)

        offset = 512
        size = 512

        path = self._createTempFile(offset + size, writeData)

        #Figure out what outcome should be
        timesInSize = int(size / dataLength) + 1
        relOffset = offset % dataLength
        expectedResultData = (writeData * timesInSize)
        expectedResultData = expectedResultData[relOffset:] + expectedResultData[:relOffset]
        expectedResultData = expectedResultData[:size]
        block = misc.readblockSUDO(path, offset, size, sudo=sudo)

        os.unlink(path)

        self.assertEquals(block[0], expectedResultData)

    def testInvalidOffsetSUDO(self):
        """
        The same as testInvalidOffset but with SUDO.
        """
        self.testInvalidOffset(True)

    def testInvalidOffset(self, sudo=False):
        """
        Make sure that we check for invalid (non 512 aligned) offset.
        """
        offset = 513
        self.assertRaises(misc.se.MiscBlockReadException, misc.readblockSUDO, "/dev/urandom", offset, 512, sudo)

    def testInvalidSizeSUDO(self):
        """
        The same as testInvalidSize but with SUDO.
        """
        self.testInvalidSize(True)

    def testInvalidSize(self, sudo=False):
        """
        Make sure that we check for invalid (non 512 aligned) size.
        """
        size = 513
        self.assertRaises(misc.se.MiscBlockReadException, misc.readblockSUDO, "/dev/urandom", 512, size, sudo)

    def testReadingMoreTheFileSizeSUDO(self):
        """
        The same as testReadingMoreTheFileSize but with SUDO.
        """
        self.testReadingMoreTheFileSize(True)

    def testReadingMoreTheFileSize(self, sudo=False):
        """
        See that correct exception is raised when trying to read more then the file has to offer.
        """
        writeData = "History, contrary to popular theories, is kings and dates and battles."

        offset = 512
        size = 512

        path = self._createTempFile(offset + size - 100, writeData)

        self.assertRaises(misc.se.MiscBlockReadIncomplete, misc.readblockSUDO, path, offset, size, sudo)

        os.unlink(path)

class CleanUpDir(vdst.BasicVDSTest):
    def testFullDir(self):
        """
        Test if method can clean a dir it should be able to.
        """
        #Populate dir
        baseDir = tempfile.mkdtemp()
        numOfFilesToCreate = 50
        for i in range(numOfFilesToCreate):
            tempfile.mkstemp(dir = baseDir)

        #clean it
        misc.cleanupdir(baseDir)

        self.assertFalse(os.path.lexists(baseDir))

    def testEmptyDir(self):
        """
        Test if method can delete an empty dir.
        """
        baseDir = tempfile.mkdtemp()

        misc.cleanupdir(baseDir)

        self.assertFalse(os.path.lexists(baseDir))

    def testNotExistingDir(self):
        """
        See that method doesn't throw a fit if given a non existing dir
        """
        misc.cleanupdir(os.path.join("I"," DONT", "EXIST"))

    def testDirWithUndeletableFiles(self):
        """
        Se that the method handles correctly a situation where it is given a dir it can't clean.
        """
        baseDir = "/proc" # This can't be deleted

        #Try and fail to clean it
        misc.cleanupdir(baseDir, ignoreErrors = True)
        self.assertTrue(os.path.lexists(baseDir))

        self.assertRaises(misc.se.MiscDirCleanupFailure, misc.cleanupdir, baseDir, False)
        self.assertTrue(os.path.lexists(baseDir))

class ReadFileSudo(vdst.BasicVDSTest):
    #For convenience. I didn't put it in the base class because I didn't want to commit.
    mycfg = property(lambda self: self.cfg[self.__class__.__name__])
    @classmethod
    def getConfigTemplate(cls):
        return {
            cls.__name__ : {
                "SudoPersona" : {"default" : "root"},
                "SudoGroup" : {"default" : "root"},
            }
        }

    def testValidInput(self):
        """
        Test if method works when given a valid file.
        """
        #make file
        writeData = "The question seldom addressed is where Medusa had snakes." + \
                    "Underarm hair is an even more embarrassing problem when it keeps biting the top of the deodorant bottle."
        fd, path = tempfile.mkstemp()
        f = os.fdopen(fd, "wb")
        f.write(writeData)
        f.close()

        #Limit file to sudo
        misc.execCmd([constants.EXT_CHOWN, "%s:%s" % (self.mycfg["SudoPersona"], self.mycfg["SudoGroup"]), path], sudo=True)
        misc.execCmd([constants.EXT_CHMOD, "400", path], sudo=True)

        #read
        readData = misc.readfileSUDO(path)

        #return permission so it can be deleted
        misc.execCmd([constants.EXT_CHMOD, "666", path], sudo=True)
        misc.execCmd([constants.EXT_CHOWN, "%d:%d" % (os.getuid(), os.getgid()), path], sudo=True)

        #clean
        os.unlink(path)

        self.assertEquals(writeData, readData[0])

    def testInvalidInput(self):
        """
        Test if method works when input is a non existing file.
        """
        fd, path = tempfile.mkstemp()
        os.unlink(path)

        self.assertRaises(misc.se.MiscFileReadException,  misc.readfileSUDO, path)

class ReadFile(vdst.BasicVDSTest):
    def testValidInput(self):
        """
        Test if method works when given a valid file.
        """
        #create
        writeData = "Trust me, I know what self-loathing is, but to kill myself? That would put a damper on my search for answers. Not at all productive."
        fd, path = tempfile.mkstemp()
        f = os.fdopen(fd, "wb")
        f.write(writeData)
        f.close()

        #read
        readData = misc.readfile(path)

        #clean
        os.unlink(path)

        self.assertEquals(writeData, readData[0])

    def testInvalidInput(self):
        """
        Test if method works when input is a non existing file.
        """
        fd, path = tempfile.mkstemp()
        os.unlink(path)

        self.assertRaises(misc.se.MiscFileReadException,  misc.readfile, path)

class PidPoll(vdst.BasicVDSTest):
    def testPidExists(self):
        """
        Test if pid given exists.
        """
        mypid = os.getpid()

        self.assertFalse(misc.pidPoll(mypid))

    def testPidDoesNotExist(self):
        """
        Test if when given incorrect input the method works correctly.
        """
        #FIXME : There is no way real way know what process aren't working.
        #I'll just try and see if there is **any** occasion where it works
        #If anyone has any idea. You are welcome to change this

        pid = os.getpid()
        result = False
        while not result:
            pid += 1
            result = misc.pidPoll(pid)

class WatchCmd(vdst.BasicVDSTest):
    #For convenience. I didn't put it in the base class because I didn't want to commit.
    mycfg = property(lambda self: self.cfg[self.__class__.__name__])
    @classmethod
    def getConfigTemplate(cls):
        return {
            cls.__name__ : {
                "EchoPath" : {"default" : "/bin/echo", "validator" : Validate.pathExists},
                "WhoamiPath" : {"default" : "/usr/bin/whoami", "validator" : Validate.pathExists},
                "SudoPersona" : {"default" : "root"},
                "PythonPath" : {"default" : "/usr/bin/python", "validator" : Validate.pathExists},
                "SleepPath" : {"default" : "/bin/sleep", "validator" : Validate.pathExists},
            }
        }

    def testExec(self):
        """
        Tests that watchCmd execs and returns the correct ret code
        """
        ret, out, err = misc.watchCmd([self.mycfg["EchoPath"]], lambda : False, 1, sudo=False)

        self.assertEquals(ret, 0)

    def testStop(self):
        """
        Test that stopping the process really works.
        """
        sleepTime = "10"
        start = time.time()
        ret, out, err = misc.watchCmd([self.mycfg["SleepPath"], sleepTime], lambda : True, 1, sudo=False)
        end = time.time()

        self.assertTrue(end - start < sleepTime, "Watched process didn't stop.")

    def testStdOut(self):
        """
        Tests that watchCmd correctly returns the standard output of the prog it executes.
        """
        line = "Real stupidity beats artificial intelligence every time."
        ret, stdout, stderr = misc.watchCmd([self.mycfg["EchoPath"], line], lambda : False, 1, sudo=False)
        self.assertEquals(stdout[0], line)

    def testStdErr(self):
        """
        Tests that watchCmd correctly returns the standard error of the prog it executes.
        """
        line = "He says gods like to see an atheist around. Gives them something to aim at."
        code = "import sys; sys.stderr.write('%s')" % line
        ret, stdout, stderr = misc.watchCmd([self.mycfg["PythonPath"], "-c", code], lambda : False, 1, sudo=False)
        self.assertEquals(stderr[0], line)

    def testSudo(self):
        """
        Tests that when running with sudo the user really is root (or other desired user).
        """
        ret, stdout, stderr = misc.watchCmd([self.mycfg["WhoamiPath"]], lambda : False, 1, sudo=True)
        self.assertEquals(stdout[0], self.mycfg["SudoPersona"])

class ExecCmd(vdst.BasicVDSTest):
    #For convenience. I didn't put it in the base class because I didn't want to commit.
    mycfg = property(lambda self: self.cfg[self.__class__.__name__])
    @classmethod
    def getConfigTemplate(cls):
        return {
            cls.__name__ : {
                "EchoPath" : {"default" : "/bin/echo", "validator" : Validate.pathExists},
                "WhoamiPath" : {"default" : "/usr/bin/whoami", "validator" : Validate.pathExists},
                "SudoPersona" : {"default" : "root"},
                "PythonPath" : {"default" : "/usr/bin/python", "validator" : Validate.pathExists},
            }
        }

    def testCommunicateWithShortLivedProcess(self):
        """
        Tests that execCmd doesn't fail when trying to communicate with a process that doesn't live long enough to accept all input.
        """
        data = "On second thought, let's not go to Camelot. It is a silly place."
        data *= 1000 #Makes the data long and thus hard to push fast

        try:
            ret, out, err = misc.execCmd(['dd', "option=crash"], data=data, sudo=False)
        except OSError, ex:
            if ex.errno == 32:
                self.fail("execCmd dind't handle this correctly")
            raise

    def testExec(self):
        """
        Tests that execCmd execs and returns the correct ret code
        """
        ret, out, err = misc.execCmd([self.mycfg["EchoPath"]], sudo=False)

        self.assertEquals(ret, 0)

    def testStdOut(self):
        """
        Tests that execCmd correctly returns the standard output of the prog it executes.
        """
        line = "All I wanted was to have some pizza, hang out with dad, and not let your weirdness mess up my day"
        ret, stdout, stderr = misc.execCmd((self.mycfg["EchoPath"], line), sudo=False)
        self.assertEquals(stdout[0], line)

    def testStdErr(self):
        """
        Tests that execCmd correctly returns the standard error of the prog it executes.
        """
        line = "Hey Scully, is this display of boyish agility turning you on at all?"
        code = "import sys; sys.stderr.write('%s')" % line
        ret, stdout, stderr = misc.execCmd([self.mycfg["PythonPath"], "-c", code], sudo=False)
        self.assertEquals(stderr[0], line)

    def testSudo(self):
        """
        Tests that when running with sudo the user really is root (or other desired user).
        """
        ret, stdout, stderr = misc.execCmd([self.mycfg["WhoamiPath"]], sudo=True)
        self.assertEquals(stdout[0], self.mycfg["SudoPersona"])

def suite():
    tests = {
        ExecCmd : [
            "testExec",
            "testSudo",
            "testStdOut",
            "testStdErr",
            "testCommunicateWithShortLivedProcess"
        ],
        WatchCmd : [
            "testExec",
            "testStop",
            "testSudo",
            "testStdOut",
            "testStdErr"
        ],
        PidPoll : [
            "testPidDoesNotExist",
            "testPidExists"
        ],
        ReadFile : [
            "testValidInput",
            "testInvalidInput"
        ],
        ReadFileSudo : [
            "testValidInput",
            "testInvalidInput"
        ],
        CleanUpDir : [
            "testFullDir",
            "testEmptyDir",
            "testNotExistingDir",
            "testDirWithUndeletableFiles"
        ],
        ReadBlockSudo : [
            "testValidInput",
            "testInvalidSize",
            "testInvalidOffset",
            "testReadingMoreTheFileSize",
            "testValidInputSUDO",
            "testInvalidSizeSUDO",
            "testInvalidOffsetSUDO",
            "testReadingMoreTheFileSizeSUDO"
        ],
        ValidateDDBytes : [
            "testValidInputTrue",
            "testValidInputFalse",
            "testInvalidInput"
        ],
        WriteBlockSUDO : [
            "testWriteToNewFile",
            "testWriteToExistingFile",
            "testInvalidOffset",
            "testInvalidText",
            "testWriteToInvalidFile",
            "testWriteToNewFileSUDO",
            "testWriteToExistingFileSUDO",
            "testInvalidOffsetSUDO",
            "testInvalidTextSUDO",
            "testWriteToInvalidFileSUDO"
        ],
        AlignData : [
            "test"
        ],
        ParseBool : [
            "testInvalidInput",
            "testValidInput"
        ],
        Checksum : [
            "testConsistency",
            "testUniqueness"
        ],
        UuidPack : [
            "test"
        ],
        ValidateUuid : [
            "testValidInput",
            "testInvalidInputNotHex",
            "testWrongLength"
        ],
        ValidateInt : [
            "testValidInput",
            "testInvalidInput"
        ],
        ValidateN : [
            "testValidInput",
            "testInvalidInput"
        ],
        DdWatchCopy : [
            "testCopy",
            "testStop",
            "testNonExistingFile",
            "testCopySUDO",
            "testStopSUDO",
            "testNonExistingFileSUDO",
            "testNonAlignedCopy"
        ],
        ParseHumanReadableSize : [
            "testValidInput",
            "testInvalidInput"
        ],
        RotateFiles : [
            "testEmptyDir",
            "testFullDir",
            "testNonExistingDir"
        ]
    }

    resSuite = ut.TestSuite()
    for testcase, methods in tests.iteritems():
        resSuite.addTests(map(testcase, methods))

    return resSuite

validateConfig = lambda cfg : vdst.validateSuiteConfig(suite(), cfg)
